import os
from typing import List, Generator

import pandas as pd
from lxml import etree

from xunter_utils.xunter_requests import ChromeClient

#创建一个电影数据存储类来保存得到的电影数据(类似于c中的结构体)  可拓展性高  数据结构类
class DoubanItem:
    detail_url:str
    titles:str
    name:str
    classes:str
    rating_num:float
    appraise_num:int
    quote:str
    img_title:str
    img_src:str

    def __str__(self):
        return '%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n%s:%s\n\n' % (
            '详情地址',self.detail_url,
            '标题',self.titles,
            '导演和演员',self.name,
            '类型',self.classes,
            '评分',self.rating_num,
            '评价人数',self.appraise_num,
            '简介',self.quote,
            '图片地址',self.img_src,
        )

class DoubanTop(ChromeClient):          #功能类
    def __init__(self, download_path='Download'):
        """
        初始化
        :param download_path:文件保存地址
        """
        self.host="https://movie.douban.com"
        self.download_path=download_path

        if not os.path.isdir(download_path):
            os.mkdir(download_path)

    #得到电影列表
    def get_list(self,page: int=1) -> Generator:
        """
        每页电影的列表数据
        :param page:
        :return:
        """
        start=(page - 1) * 25
        res = self.send_request(f'{self.host}/top250?start={start}&filter=')
        # print(res)
        #数据解析部分 html数据， xpath来完成数据解析
        html = etree.HTML(res.text)     #把字符串数据变为html树形结构对象

        grid_view = html.xpath('//ol[@class="grid_view"]/li')  # 获取任意位置class为grid_view的ol标签里的所有li

        # res_data = []  #保存电影数据  通过yield生成器优化就不需要将数据保存到列表中 而是调用方法时取一个调一个
        # 获取每个li结构对象
        for grid in grid_view:
            item = DoubanItem()
            item.detail_url = grid.xpath('.//div[@class="hd"]/a/@href')[0]  # 自己通过分析需要获取信息的标签和标签的上级标签写的xpath

            item.titles = grid.xpath(
                './/div[@class="hd"]/a/span/text()')  # 获取标题  注意这里要找到数据所在的具体标签  数据所在标签不给下标的话会返回一个列表 包含所有为该标签的数据

            item.name = grid.xpath('.//div[@class="bd"]/p/br/preceding-sibling::text()')[0].strip()  #strip()删除两边的空格# 导演和演员信息 这里以br为分界线向上取‘哥哥’的数据

            item.classes = grid.xpath('.//div[@class="bd"]/p/br/following-sibling::text()')[0].strip()  # 时间产地分类信息  向下取‘弟弟’信息

            item.rating_num = grid.xpath('.//span[@class="rating_num"]/text()')[0]

            item.appraise_num = grid.xpath('.//div[@class="star"]/span[last()]/text()')[0]

            # item.quote = grid.xpath('.//span[@class="inq"]/text()')[0]
            quote = grid.xpath('.//span[@class="inq"]/text()')
            if quote:
                item.quote = quote[0]
            else:
                item.quote = ''

            item.img_title = grid.xpath('.//div[@class="pic"]//img/@alt')[0]  # 获取class为pic里的img标签里面的alt属性值  图片名称

            item.img_src = grid.xpath('.//div[@class="pic"]//img/@src')[0]  # 图片地址

        #优化   通过yield生成器优化就不需要将数据保存到列表中 而是调用方法时取一个调一个
            yield item

        #     res_data.append(item)  #将对象保存到列表中去
        #
        # return res_data

    def save_txt(self,file_name:str,data:DoubanItem):
        """
        保存豆瓣电影数据对象到文本中
        :param file_name: 文件名
        :param data: 豆瓣电影数据对象
        :return:
        """
        with open(os.path.join(
            self.download_path,
            file_name
        ),'a',encoding='utf-8') as f:
            f.write(str(data))

    def save_img(self,file_name:str,data:bytes):
        """
        保存图片
        :param file_name:图片地址
        :param data: 图片数据
        :return:
        """
        with open(os.path.join(
            self.download_path,
            file_name
        ),'wb',) as f:
            f.write(data)

#创建豆瓣Excel管道类将豆瓣数据保存到Excel中
class DoubanExcelPipeLine:
    def __init__(self,file_path:str):
        """
        初始化
        :param file_path:保存的文件地址
        """

        #创建一个writer文件写入对象 数据写入文件
        self.writer = pd.ExcelWriter(file_path)
        self.data = []

    def process_item(self,item:DoubanItem):
        """
        数据处理  将豆瓣数据对象存入到self.data中
        :param item:豆瓣数据对象
        :return:
        """
        self.data.append({
            '详情地址': item.detail_url,
            '标题': item.titles,
            '导演和演员': item.name,
            '类型': item.classes,
            '评分': item.rating_num,
            '评价人数': item.appraise_num,
            '简介': item.quote,
            '图片地址': item.img_src,
        })
        return item

    def close_spider(self):
        """
        爬虫结束后将数据写入到Excel文件中 并且关闭文件
        :return:
        """
        df = pd.DataFrame(self.data)
        df.to_excel(self.writer,index=False)
        self.writer.close()



if __name__ == '__main__':
    client=DoubanTop()  #爬虫类
    pipline = DoubanExcelPipeLine(
        os.path.join(
            client.download_path,
            '豆瓣TOP250.xlsx'
        )
    )

    # for p in range(1,12):
    #     print(f'开始抓取第{p}页')
    #     for i in client.get_list(p):  # 这样子我们调用的方法回一次性得到所有的电影信息，但是我们去保存看到是一个一个的写入到文件中去的
    #         print(i.titles)
    #         client.save_txt('top.txt', i)  # 通过yield生成器优化 在调用方法时不会全部获取数据而是用一个取一个
    #         img_res = client.send_request(i.img_src)
    #         client.save_img(i.img_title + '.jpg', img_res.content)

    for p in range(1,3):
        print(f'开始抓取第{p}页')
        for i in client.get_list(p):
            pipline.process_item(i)

    pipline.close_spider()
